/*
* Description: 窗口检测
*/

#include "application_monitor.h"
#include "common/guest_utils.h"
#include "common.h"
#include "common/session_utils.h"
#include "regex"
#include "text_util/string_proc.h"

using namespace COMMON;

namespace GUEST {
const std::string REGEX = R"(^(?:[a-zA-Z]:\\)?(?:[^<>:"/\\|?*\0\r\n]+\\)*[^<>:"/\\|?*\0\r\n]*$)";
const std::regex PATTERN(REGEX);
const char* APPLICATION_WHITELIST_CONFIG_FILE = "C:\\ProgramData\\VMAgent\\config\\AutoPauseList.json";
const DWORD NO_USER_LOGON_SESSION = 0xFFFFFFFF;

void ApplicationMonitor::Init()
{
    SessionUtils::RegSessionChangeCallback(sessionName_,
        std::bind(&ApplicationMonitor::SetSessionState, &APPLICATION_MONITOR, std::placeholders::_1));
    SetUsernameAndPath();
    InitWhiteListCollection();
}

void ApplicationMonitor::SetSessionState(int32_t state)
{
    LOG_INFO("set session state {}", state);
    sessionState_.store(state);
    if (WTS_SESSION_LOGON == sessionState_.load() || WTS_SESSION_LOCK == sessionState_.load() ||
        WTS_SESSION_UNLOCK == sessionState_.load()) {
        SetUsernameAndPath();
    }
}

void ApplicationMonitor::SetUsernameAndPath()
{
    std::string name = "";
    if (GuestUtils::GetCurrentUsername(name)) {
        char combinePath[MAX_PATH] = { 0 };
        if (sprintf_s(combinePath, MAX_PATH, APPLICATION_WINDOW_HELPER_FILE_PATH, name.c_str()) < 0) {
            LOG_ERR("Error occurred, writing combinePath failed!");
        } else {
            resultFilePath_ = std::string(combinePath);
        }
    }
}

bool ApplicationMonitor::isFilePathValid(const std::string &path)
{
    return path.length() <= MAX_PATH && std::regex_match(path, PATTERN);
}

bool ApplicationMonitor::IsApplicationOpenedInTaskBar()
{
    LOG_INFO("current session state: {} ", sessionState_.load());
    if (WTS_CONSOLE_CONNECT == sessionState_.load() || WTS_SESSION_LOGOFF == sessionState_.load()) {
        LOG_INFO("user log out");
        return false;
    }
    DWORD exitCode;
    GuestUtils::ExecCmdWithExitCode(APPLICATION_WINDOW_HELPER_EXE_PATH, true, exitCode);
    if (exitCode != SUCCESS_EXIT_CODE) {
        LOG_ERR("application_window_helper execute failed exitCode: {}", exitCode);
        return true;
    }

    if (isFilePathValid(resultFilePath_) == INVALID_FILE_ATTRIBUTES) {
        LOG_ERR("result path is invalid");
        SetUsernameAndPath();
    }

    HANDLE hRead = CreateFile(resultFilePath_.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL, nullptr);
    if (hRead == INVALID_HANDLE_VALUE) {
        LOG_ERR("application_window_helper create file failed");
        return true;
    }

    DWORD dataSize = GetFileSize(hRead, nullptr);
    char* buffer = (char*) malloc(dataSize);
    if (buffer == nullptr) {
        CloseHandle(hRead);
        return true;
    }

    SecureZeroMemory(buffer, dataSize);
    DWORD dataReaded = 0;
    bool res = false;
    if (ReadFile(hRead, buffer, dataSize, &dataReaded, nullptr)) {
        if (strncmp(buffer, TRUE_STR, strlen(TRUE_STR)) == 0) {
            LOG_INFO("application_window_helper detect window opened");
            res = true;
        }
    }
    free(buffer);
    CloseHandle(hRead);
    return res;
}

void ApplicationMonitor::Quit()
{
    SessionUtils::UnRegSessionChangeCallback(sessionName_);
}

bool ApplicationMonitor::InitWhiteListCollection()
{
    std::string config = "";
    applicationWhiteList_.clear();
    if (!LoadWhiteListConfigFile(config)) {
        LOG_ERR("[ApplicationMonitor] read application config file error!!!");
        return false;
    }
    if (!ParseConfigListToCollection(config.c_str())) {
        LOG_ERR("[ApplicationMonitor] parse config file to collection error!!!");
        return false;
    }
    return true;
}

bool ApplicationMonitor::LoadWhiteListConfigFile(std::string& config)
{
    HANDLE configFileHandle = CreateFile(APPLICATION_WHITELIST_CONFIG_FILE, GENERIC_READ, 0, nullptr, OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL, nullptr);
    if (configFileHandle == INVALID_HANDLE_VALUE) {
        LOG_ERR("[ApplicationMonitor] error occurred, file handle is nullptr, error code: {}", GetLastError());
        return false;
    }
    DWORD size = GetFileSize(configFileHandle, nullptr);
    DWORD sizeReaded = 0;
    char* data = new (std::nothrow) char[size + 1];
    if (data == nullptr) {
        LOG_ERR("[ApplicationMonitor] error occurred, allocate memory failed!");
        if (configFileHandle != INVALID_HANDLE_VALUE) {
            CloseHandle(configFileHandle);
            configFileHandle = INVALID_HANDLE_VALUE;
        }
        return false;
    }
    SecureZeroMemory(data, size + 1);
    if (!ReadFile(configFileHandle, data, size, &sizeReaded, nullptr) || sizeReaded != size) {
        LOG_ERR("[ApplicationMonitor] error occurred, reading file failed, error code: {}", GetLastError());
        if (configFileHandle != INVALID_HANDLE_VALUE) {
            CloseHandle(configFileHandle);
            configFileHandle = INVALID_HANDLE_VALUE;
        }
        delete[] data;
        return false;
    }
    if (configFileHandle != INVALID_HANDLE_VALUE) {
        CloseHandle(configFileHandle);
        configFileHandle = INVALID_HANDLE_VALUE;
    }
    config = std::string(data);
    delete[] data;
    return true;
}

bool ApplicationMonitor::ParseConfigListToCollection(const char* data)
{
    cJSON* root = cJSON_Parse(data);
    if (!root) {
        LOG_ERR("[ApplicationMonitor] parse config data failed!!!");
        return false;
    }
    if (root->type != cJSON_Array) {
        LOG_ERR("[ApplicationMonitor] parse config data error, is not json array!!!");
        cJSON_Delete(root);
        return false;
    }
    int32_t size = cJSON_GetArraySize(root);
    for (int32_t i = 0; i < size; i++) {
        cJSON* item = cJSON_GetArrayItem(root, i);
        if (item != NULL && item->type == cJSON_String) {
            std::string appName = item->valuestring;
            std::wstring wAppName = StringProc::StringToWStringByUTF8(appName);
            applicationWhiteList_.insert(wAppName);
        }
    }
    cJSON_Delete(root);
    LOG_ERR("[ApplicationMonitor] parse config file success!!!");
    return true;
}

bool ApplicationMonitor::IsRunningApplicationInWhiteList()
{
    DWORD currentSessionId = WTSGetActiveConsoleSessionId();
    if (currentSessionId == NO_USER_LOGON_SESSION) {
        LOG_INFO("[ApplicationMonitor] current no user logon!!!");
        return false;
    }
    if (applicationWhiteList_.empty()) {
        LOG_ERR("[ApplicationMonitor] error occurred, app white list empty.");
        InitWhiteListCollection();
        return true;
    }
    DWORD level = 1;
    bool isInWhiteList = false;
    PWTS_PROCESS_INFO_EXW pwtsProcessInfo = NULL;
    DWORD ptwsProcessCount = 0;
    if (!WTSEnumerateProcessesExW(WTS_CURRENT_SERVER_HANDLE, &level, currentSessionId,
        (LPWSTR*)&pwtsProcessInfo, &ptwsProcessCount)) {
        LOG_ERR("[ApplicationMonitor] Enumerate process api failed, error code {}", GetLastError());
        return true;
    }
    bool isAppInWhiteList = false;
    // 遍历进程列表
    for (DWORD i = 0; i < ptwsProcessCount; i++) {
        if (pwtsProcessInfo[i].SessionId == currentSessionId) {
            std::wstring processNameW = pwtsProcessInfo[i].pProcessName;
            std::transform(processNameW.begin(), processNameW.end(), processNameW.begin(), ::tolower);
            if (applicationWhiteList_.find(processNameW) != applicationWhiteList_.end()) {
                isAppInWhiteList = true;
                break;
            }
        }
    }
    if (pwtsProcessInfo != NULL) {
        WTSFreeMemory(pwtsProcessInfo);
        pwtsProcessInfo = NULL;
    }
    return isAppInWhiteList;
}
}  //namespace GUEST